/* 
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. 
 * 
 * This file is available and licensed under the following license:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *   * Neither the name of Sun Microsystems nor the names of its contributors 
 *     may be used to endorse or promote products derived from this software 
 *     without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package effectsplayground;

import javafx.animation.*;
import javafx.animation.transition.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.text.*;
import javafx.stage.*;
import javafx.reflect.*;
import effectsplayground.EffectControls.*;
import effectsplayground.control.*;

/**
 * @author campbell
 */

def width = (6 * (82 + 10)) + 20;

def canvasWidth = width-10;
def canvasHeight = 275;

var stage:Stage;

var inBrowser = "true".equals(FX.getArgument("isApplet") as String);
var dragTextVisible =
    bind inBrowser and AppletStageExtension.appletDragSupported;
var closeButtonVisible = bind (not inBrowser);

var selectedPreview:Preview = null on replace {
    if (selectedPreview == null) {
        hideControls();
    }
};

var fgUrl = "{__DIR__}images/flower.jpg";
var bgUrl = "{__DIR__}images/water.jpg";

package var fgImage = bind Image {
    url: fgUrl
    width: canvasWidth * 0.9
    height: canvasHeight * 0.9
    preserveRatio: true
};
package var bgImage = bind Image {
    url: bgUrl
    width: canvasWidth * 0.9
    height: canvasHeight * 0.9
    preserveRatio: true
};

var fgImageView = ImageView {
    smooth: true
    image: bind fgImage
};
var canvasImage = bind if (selectedPreview != null) selectedPreview.control.canvasImage else fgImageView;

def twidth = 82;
def theight = 71;
package var fgThumb = bind Image {
    url: fgUrl
    width: twidth
    height: theight
    preserveRatio: true
};
package var bgThumb = bind Image {
    url: bgUrl
    width: twidth
    height: theight
    preserveRatio: true
};

var firstRowControlsVisible = false;
var secondRowControlsVisible = false;

var rotateKnob:CustomKnob;

class Canvas extends CustomNode {
    override protected function create() : Node {
        Group {
            clip: Rectangle { smooth: false width: canvasWidth height: canvasHeight + 1 }
            content: [
                Rectangle {
                    width: canvasWidth
                    height: canvasHeight
                    fill: LinearGradient {
                        startX: 0
                        startY: 0
                        endX: 0
                        endY: 1
                        stops: [
                            Stop { offset: 0.1 color: Color.BLACK },
                            Stop { offset: 1.0 color: Color.rgb(193, 193, 193) },
                        ]
                    }
                },
                Group {
                    cache: true
                    translateX: bind (canvasWidth - canvasImage.layoutBounds.width) / 2 -
                        canvasImage.layoutBounds.minX
                    translateY: bind (canvasHeight / 2) - (fgImage.height / 2)
                    content: bind canvasImage
                    rotate: bind (360.0 - rotateKnob.value)
                },
                Rectangle {
                    width: canvasWidth
                    height: 2
                    fill: Color.rgb(103, 103, 103)
                },
                Rectangle {
                    y: canvasHeight
                    width: canvasWidth
                    height: 1
                    fill: Color.rgb(240, 240, 240)
                }
            ]
            onMouseClicked: function(e) {
                hideControls();
            }
        }
    }
}

class ControlPlate extends CustomNode {
    public var controlGroup:Node;

    override protected function create() : Node {
        Group {
            content: [
                Rectangle {
                    width: width - 10
                    height: 56
                    fill: LinearGradient {
                        startX: 0
                        startY: 0
                        endX: 0
                        endY: 1
                        stops: [
                            Stop { offset: 0.0  color: Color.rgb(102, 102, 102) },
                            Stop { offset: 0.5  color: Color.rgb(148, 148, 148) },
                            Stop { offset: 1.0  color: Color.rgb(102, 102, 102) },
                        ]
                    }
                },
                Group {
                    content: bind controlGroup
                }
            ]
        }
    }
};

class OpenFileFilter extends javax.swing.filechooser.FileFilter {
    override public function getDescription() : String {
        return "JPG and PNG images";
    }

    override public function accept(f: java.io.File) : Boolean {
        return f.isDirectory()
            or f.getName().endsWith(".png")
            or f.getName().endsWith(".jpg");
    }
}

class SaveFileFilter extends javax.swing.filechooser.FileFilter {
    override public function getDescription() : String {
        return "PNG images";
    }

    override public function accept(f: java.io.File) : Boolean {
        return f.isDirectory() or f.getName().endsWith(".png");
    }
}

class Footer extends CustomNode {
    var fileChooser:javax.swing.JFileChooser;

    function getFileChooser(open:Boolean) : javax.swing.JFileChooser {
        if (fileChooser == null) {
            fileChooser = javax.swing.JFileChooser {};
        }
        var filter = if (open) OpenFileFilter {} else SaveFileFilter {};
        fileChooser.setFileFilter(filter);
        return fileChooser;
    }

    override protected function create() : Node {
        var xoff = 28;
        var yoff = 9;
        var xspc = 10;
        Group {
            content: [
                Path {
                    elements: [
                        MoveTo { x: 0 y: 0 },
                        LineTo { x: width y: 0 },
                        LineTo { x: width y: 27 },
                        QuadCurveTo { x: width - 3 y: 33 controlX: width controlY: 33 },
                        QuadCurveTo { x: 3 y: 33 controlX: width / 2, controlY: 100 },
                        QuadCurveTo { x: 0 y: 30 controlX: 0, controlY: 33 },
                    ]
                    stroke: null
                    fill: LinearGradient {
                        startX: 0
                        startY: 0
                        endX: 0
                        endY: 1
                        stops: [
                            Stop { offset: 0.0  color: Color.rgb( 8,  8,  8) },
                            Stop { offset: 0.25 color: Color.rgb(17, 17, 17) },
                            Stop { offset: 0.28 color: Color.rgb(17, 17, 17) },
                            Stop { offset: 0.5  color: Color.rgb(10, 10, 10) }
                        ]
                    }
                },
                Button {
                    translateX: xoff
                    translateY: yoff
                    text: "Open Image 1"
                    action: function() {
                        var fc = getFileChooser(true);
                        if (fc.showOpenDialog(null) == javax.swing.JFileChooser.APPROVE_OPTION) {
                            fgUrl = fc.getSelectedFile().toURI().toString();
                        }
                    }
                },
                Button {
                    translateX: xoff + Button.width + xspc
                    translateY: 10
                    text: "Open Image 2"
                    action: function() {
                        var fc = getFileChooser(true);
                        if (fc.showOpenDialog(null) == javax.swing.JFileChooser.APPROVE_OPTION) {
                            bgUrl = fc.getSelectedFile().toURI().toString();
                        }
                    }
                },
                Button {
                    translateX: width - xoff - (2*Button.width) - xspc
                    translateY: 10
                    text: "Save Image"
                    action: function() {
                        var fc = getFileChooser(false);
                        fc.setSelectedFile(new java.io.File("funpix.png"));
                        if (fc.showSaveDialog(null) == javax.swing.JFileChooser.APPROVE_OPTION) {
                            saveImage(canvasImage, fc.getSelectedFile());
                        }
                    }
                },
                Button {
                    translateX: width - xoff - Button.width
                    translateY: 10
                    text: "Remove Effect"
                    action: function() {
                        selectedPreview = null;
                        hideControls();
                    }
                },
                rotateKnob = CustomKnob {
                    translateX: width/2
                    translateY: 30
                    minimum:    0.0
                    maximum:  360.0
                    value:      0.0
                    minAngle:   0.0
                    maxAngle: 360.0
                },
                Rectangle {
                    fill: Color.rgb(40, 40, 40)
                    width: width
                    height: 1
                }
            ]
        }
    }
}

package class Preview extends CustomNode {
    public-init var label:String = null;
    public-init var control:EffectControl = null on replace {
        control.previewNode = this;
    };
    public-init var firstRow = true;

    var selected = bind isSameObject(this, selectedPreview);
    var buttonVisible = false;
    var buttonOpacity = 0.0;
    
    def selColor = Color.WHITE;
    var topColor = Color.color(0.3, 0.3, 0.3);

    def colorFade:Timeline = Timeline {
        keyFrames: [
            KeyFrame {
                time: 0s
                values: [
                    topColor => Color.color(0.3, 0.3, 0.3)
                    buttonOpacity => 0.0
                ]
                action: function() {
                    buttonVisible = (colorFade.rate > 0);
                }
            },
            KeyFrame {
                time: 0.2s
                values: [
                    topColor => Color.WHITE
                    buttonOpacity => 1.0
                ]
            }
        ]
    }

    override protected function create() : Node {
        var rect:Rectangle;
        var text:Text;
        Group {
            blocksMouse: true
            cursor: Cursor.HAND
            content: [
                Group {
                    clip: Rectangle { smooth: false width: twidth height: theight }
                    cache: true
                    content: [
                    Group {
                        translateX: bind (twidth - control.thumbImage.layoutBounds.width) / 2 -
                            control.thumbImage.layoutBounds.minX
                        translateY: 0
                        content: bind control.thumbImage
                    }
                    rect = Rectangle {
                        y: theight * 0.72
                        width: twidth
                        height: theight * 0.28
                        fill: Color.color(0, 0, 0, 0.7)
                        stroke: Color.BLACK
                    },
                    text = Text {
                        translateX: bind (twidth - text.layoutBounds.width) / 2 - text.layoutBounds.minX
                        translateY: bind rect.layoutBounds.minY + (rect.layoutBounds.height / 2) + 4
                        font: Font { size: 10 }
                        content: label
                        fill: Color.WHITE
                    },
                    ]
                },
                Rectangle {
                    width: twidth
                    height: theight
                    fill: Color.TRANSPARENT
                    stroke: bind if (selected) selColor else topColor
                },
                Polygon {
                    visible: bind selected
                    translateX: twidth / 2
                    translateY: -7
                    points: [0, 0, 5, 5, -5, 5]
                    fill: Color.rgb(190, 190, 190)
                },
                Polygon {
                    visible: bind selected
                    translateX: twidth / 2
                    translateY: theight + 3
                    points: [-5, 0, 5, 0, 0, 5]
                    fill: Color.rgb(190, 190, 190)
                },
            ]
            onMouseEntered: function(e) {
                colorFade.rate = 1;
                colorFade.play();
            }
            onMouseExited: function(e) {
                colorFade.rate = -1;
                colorFade.play();
            }
            onMouseClicked: function(e) {
                if (firstRow) {
                    if (not isSameObject(selectedPreview, this)) {
                        selectedPreview = this;
                        showFirstRowControls();
                    } else {
                        toggleFirstRowControls();
                    }
                } else {
                    if (not isSameObject(selectedPreview, this)) {
                        selectedPreview = this;
                        showSecondRowControls();
                    } else {
                        toggleSecondRowControls();
                    }
                }
            }
        }
    }
}

function saveImage(node:Node, file:java.io.File) {
    // NOTE: The following code uses internal implementation details
    // that will most certainly change in a future JavaFX release, so
    // please do not copy this code.  We will likely add a proper API
    // for saving images in a later release.
    
    var context = FXLocal.getContext();
    var nodeClass = context.findClass("javafx.scene.Node");
    var getFXNode = nodeClass.getFunction("impl_getFXNode");
    var sgNode = (getFXNode.invoke(context.mirrorOf(node)) as FXLocal.ObjectValue).asObject();
    var getBounds = sgNode.getClass().getMethod("getBounds");
    var bounds = getBounds.invoke(sgNode) as java.awt.geom.Rectangle2D;
    var g2dClass = (context.findClass("java.awt.Graphics2D") as FXLocal.ClassType).getJavaImplementationClass();
    var paintMethod = sgNode.getClass().getMethod("render", g2dClass);

    var w = bounds.getWidth() as Integer;
    var h = bounds.getHeight() as Integer;
    var img = new java.awt.image.BufferedImage(w, h,
        java.awt.image.BufferedImage.TYPE_INT_ARGB);
    var g2 = img.createGraphics();
    g2.translate(-bounds.getX(), -bounds.getY());
    paintMethod.invoke(sgNode, g2);
    g2.dispose();

    var savefile;
    if (not file.getName().toLowerCase().endsWith(".png")) {
        savefile = new java.io.File(file.getParent(), "{file.getName()}.png");
    } else {
        savefile = file;
    }

    javax.imageio.ImageIO.write(img, "png", savefile);
}

package var canvas:Canvas = Canvas {
    translateX: 5
    translateY: 25
};

var firstRowStartY  = bind canvas.boundsInParent.maxY;
var secondRowStartY = bind canvas.boundsInParent.maxY + theight + 22;
var footerStartY    = bind canvas.boundsInParent.maxY + (theight * 2) + 41;

var firstRowPreviews = HBox {
    translateX: 10
    translateY: 10
    spacing: 9
    content: [
        Preview {
            label: "Blend"
            control: BlendControl { }
        },
        Preview {
            label: "Blur"
            control: BlurControl { }
        },
        Preview {
            label: "Motion Blur"
            control: MotionBlurControl { }
        },
        Preview {
            label: "Bloom"
            control: BloomControl { }
        },
        Preview {
            label: "Glow"
            control: GlowControl { }
        },
        Preview {
            label: "Color Adjust"
            control: ColorAdjustControl { }
        },
    ]
};

var secondRowPreviews = HBox {
    translateX: 10
    translateY: 10
    spacing: 9
    content: [
        Preview {
            label: "Drop Shadow"
            control: DropShadowControl { }
            firstRow: false
        },
        Preview {
            label: "Inner Shadow"
            control: InnerShadowControl { }
            firstRow: false
        },
        Preview {
            label: "Perspective"
            control: PerspectiveControl { }
            firstRow: false
        },
        Preview {
            label: "Lighting"
            control: LightingControl { }
            firstRow: false
        },
        Preview {
            label: "Sepia Tone"
            control: SepiaControl { }
            firstRow: false
        },
        Preview {
            label: "Reflection"
            control: ReflectionControl { }
            firstRow: false
        },
    ]
};

var firstRow = Group {
    content: [
        Group {
            translateX: 0
            translateY: bind firstRowStartY
            content: [
                Rectangle {
                    width: width
                    height: theight + 21
                    fill: LinearGradient {
                        startX: 0
                        startY: 0
                        endX: 0
                        endY: 1
                        stops: [
                            Stop { offset: 0.0  color: Color.rgb(107, 107, 107) },
                            Stop { offset: 0.95 color: Color.BLACK },
                        ]
                    }
                },
                firstRowPreviews,
            ]
        }
    ]
};

var secondRow = Group {
    content: [
        Group {
            translateX: 0
            translateY: bind secondRowStartY
            content: [
                Rectangle {
                    width: width
                    height: theight + 21
                    fill: LinearGradient {
                        startX: 0
                        startY: 0
                        endX: 0
                        endY: 1
                        stops: [
                            Stop { offset: 0.0  color: Color.rgb(107, 107, 107) },
                            Stop { offset: 0.95 color: Color.BLACK },
                        ]
                    }
                },
                Rectangle {
                    x: 0
                    y: -1
                    width: width
                    height: 1
                    fill: Color.rgb(177, 177, 177)
                },
                Rectangle {
                    x: 0
                    y: 0
                    width: width
                    height: 1
                    fill: Color.rgb(80, 80, 80)
                },
                secondRowPreviews,
            ]
        }
    ]
};

var footer = Footer {
    translateY: bind footerStartY
};

var firstRowSlide = TranslateTransition {
    node: firstRow
    fromY: 0
    toY: -55
    duration: 200ms
    interpolate: Interpolator.EASEBOTH
};

var secondRowSlide = TranslateTransition {
    node: secondRow
    fromY: 0
    toY: -55
    duration: 200ms
    interpolate: Interpolator.EASEBOTH
};

var firstRowExpanded = false;
var secondRowExpanded = false;

function expandFirstRow() {
    firstRowSlide.rate = 1;
    firstRowSlide.play();
    firstRowExpanded = true;
}

function collapseFirstRow() {
    firstRowSlide.rate = -1;
    firstRowSlide.play();
    firstRowExpanded = false;
}

function expandSecondRow() {
    secondRowSlide.rate = 1;
    secondRowSlide.play();
    secondRowExpanded = true;
}

function collapseSecondRow() {
    secondRowSlide.rate = -1;
    secondRowSlide.play();
    secondRowExpanded = false;
}

function showFirstRowControls() {
    if (not firstRowExpanded) {
        expandFirstRow();
    }
    if (secondRowExpanded) {
        collapseSecondRow();
    }
    firstRowControlsVisible = true;
    secondRowControlsVisible = false;
}

function showSecondRowControls() {
    if (not firstRowExpanded) {
        expandFirstRow();
    }
    if (not secondRowExpanded) {
        expandSecondRow();
    }
    firstRowControlsVisible = false;
    secondRowControlsVisible = true;
}

function hideControls() {
    if (firstRowExpanded) {
        collapseFirstRow();
    }
    if (secondRowExpanded) {
        collapseSecondRow();
    }
    firstRowControlsVisible = false;
    secondRowControlsVisible = false;
}

function toggleFirstRowControls() {
    if (firstRowControlsVisible) {
        hideControls();
    } else {
        showFirstRowControls();
    }
}

function toggleSecondRowControls() {
    if (secondRowControlsVisible) {
        hideControls();
    } else {
        showSecondRowControls();
    }
}

var firstControlPlate = ControlPlate {
    translateX: 5
    translateY: bind secondRowStartY - 56
    controlGroup: bind if (selectedPreview.firstRow) selectedPreview.control.controlsGroup else null
};

var secondControlPlate = ControlPlate {
    translateX: 5
    translateY: bind footerStartY - 56
    controlGroup: bind if (not selectedPreview.firstRow) selectedPreview.control.controlsGroup else null
};

var stageX = 200.0;
var stageY = 200.0;

var detachButton:DetachButton;
var dragRect:Rectangle;
var dragText:Text;
var dragTextOpacity = 0.0;
var dragTextFader = Timeline {
    keyFrames: [
        at (0s) {
            dragTextOpacity => 0.0
        },
        at (0.2s) {
            dragTextOpacity => 1.0
        }
    ]
};

var topPanel:Group = Group {
    content: [
        Path {
            elements: [
                MoveTo { x: 3 y: 0 },
                LineTo { x: width-3 y: 0 },
                QuadCurveTo { x: width y: 3 controlX: width controlY: 0 },
                LineTo { x: width y: bind firstRow.layoutBounds.minY },
                LineTo { x: 0 y: bind firstRow.layoutBounds.minY },
                LineTo { x: 0 y: 3 },
                QuadCurveTo { x: 3 y: 0 controlX: 0 controlY: 0 }
            ]
            stroke: null
            fill: LinearGradient {
                startX: 0
                startY: 0
                endX: 0
                endY: 1
                stops: [
                    Stop { offset: 0.0  color: Color.rgb(126, 127, 134) },
                    Stop { offset: 0.01 color: Color.rgb( 26,  26,  26) },
                    Stop { offset: 0.65 color: Color.BLACK },
                    Stop { offset: 1.0  color: Color.rgb(107, 107, 107) },
                ]
            }
        },

        dragRect = Rectangle {
            visible: bind dragTextVisible or (not inBrowser)
            cursor: Cursor.HAND
            width: width-40
            height: 20
            fill: Color.TRANSPARENT
            onMouseEntered: function(e) {
                dragTextFader.rate = 1;
                dragTextFader.play();
            }
            onMouseExited: function(e) {
                dragTextFader.rate = -1;
                dragTextFader.play();
            }
            onMouseDragged: function(e) {
                stageX += e.dragX;
                stageY += e.dragY;
            }
        },

        CloseButton {
            visible: bind closeButtonVisible
            translateX: width - 22
            translateY: 9
            onMouseClicked: function(e) {
                stage.close();
            }
        },

        dragText = Text {
            visible: bind dragTextVisible
            opacity: bind dragTextOpacity
            translateX: bind (width - dragText.layoutBounds.width) / 2
            translateY: 18
            content: "You can drag me out of the browser"
            font: Font { size: 10 }
            fill: Color.WHITE
        },
        detachButton = DetachButton {
            visible: bind dragTextVisible
            translateX: width - 22
            translateY: 9
        },

        firstControlPlate,
        secondControlPlate,
        canvas,
        firstRow,
        secondRow,
    ]
};

function run() {
stage = Stage {
    title: "Effects Playground"
    x: bind stageX;
    y: bind stageY;
    resizable: false
    style: StageStyle.TRANSPARENT
    scene: Scene {
        fill: bind if (inBrowser) Color.WHITE else Color.TRANSPARENT
        content: [
            topPanel,
            footer
        ]
    }
    extensions: [
        AppletStageExtension {
            shouldDragStart: function(e): Boolean {
                return inBrowser and e.primaryButtonDown and (dragRect.hover or detachButton.hover);
            }
            onDragStarted: function() {
                inBrowser = false;
            }
            onAppletRestored: function() {
                inBrowser = true;
            }
            useDefaultClose: false
        }
    ]
}
}
